package pers.chao.logger.plugin.logback;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.filter.LevelFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.OutputStreamAppender;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import ch.qos.logback.core.spi.FilterReply;
import ch.qos.logback.core.util.FileSize;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pers.chao.logger.enums.AppenderGroupMode;
import pers.chao.logger.enums.ColorfulLogTheme;
import pers.chao.logger.enums.LogFileType;
import pers.chao.logger.exception.AppenderGroupModeChooseException;
import pers.chao.logger.properties.ColorfulLoggerProperties;
import pers.chao.logger.properties.LogbackLogbackProperties;
import pers.chao.tool.spring.BeansUtil;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * Description: logback logger配置工具
 *
 * @author W.Chao
 * @date 2020/11/22 0:34
 **/
public class LoggerBuilder {
	private static final String CONSOLE_APPENDER_NAME = "SERVE-CONSOLE";
	private static ConsoleAppender defaultConsoleAppender = null;

	private static ColorfulLoggerProperties colorfulLoggerProperties = BeansUtil.getBean("colorfulLoggerProperties");
	private static LogbackLogbackProperties logbackLogbackProperties = BeansUtil.getBean("logbackLogbackProperties");


	{
		Map<String, Appender<ILoggingEvent>> appenderMap = logbackAllConfigurationAppender();
		appenderMap.entrySet().stream()
				.filter((entry) -> {
					boolean match = false;
					Appender<ILoggingEvent> appender = entry.getValue();
					// 如果logback配置文件中，已存在控制台输出的appender，则直接使用；不存在则重新生成
					if (appender instanceof ConsoleAppender) {
						defaultConsoleAppender = (ConsoleAppender) appender;
						match = true;
					}
					return match;
				}).findAny();
	}

	// *******************************************************
	// 控制台日志和文件日志                                    *
	// *******************************************************

	/**
	 * 获取自定义的logger日志，在指定日志文件logFileName.getType()中输出日志，
	 * 日志中会包括所有线程及方法堆栈信息
	 *
	 * @param logFileName  log文件名，可根据该类型形成不同的日志文件
	 * @param clazz
	 * @param consoleLevel 控制台日志级别
	 * @return
	 */
	public static Logger getLogger(Class clazz, LogFileType logFileName, Level consoleLevel) {
		ch.qos.logback.classic.Logger logger = getLogbackLogger(clazz);
		LoggerContext loggerContext = logger.getLoggerContext();
		List<OutputStreamAppender> appenderList = chooseAppender(AppenderGroupMode.CONSOLE_AND_FILE, logFileName, loggerContext, consoleLevel);
		setLoggerAppend(logger, appenderList);
		return logger;
	}

	/**
	 * 获取自定义的logger日志，在指定日志文件logFileName.getType()中输出日志，
	 * 日志中会包括所有线程及方法堆栈信息
	 *
	 * @param logFileName log文件名，可根据该类型形成不同的日志文件
	 * @param clazz
	 * @return
	 */
	public static Logger getLogger(Class clazz, LogFileType logFileName) {
		// 控制台日志级别默认为debug
		return getLogger(clazz, logFileName, Level.DEBUG);
	}

	// *******************************************************
	// 文件日志                                               *
	// *******************************************************

	/**
	 * 获取自定义的logger日志，在指定日志文件logFileName.getType()中输出日志，
	 * 日志中会包括所有线程及方法堆栈信息
	 *
	 * @param logFileName log文件名，可根据该类型形成不同的日志文件
	 * @param clazz
	 * @return
	 */
	public static Logger getLogger(LogFileType logFileName, Class clazz) {
		ch.qos.logback.classic.Logger logger = getLogbackLogger(clazz);
		LoggerContext loggerContext = logger.getLoggerContext();
		List<OutputStreamAppender> appenderList = chooseAppender(AppenderGroupMode.ONLY_FILE, logFileName, loggerContext, null);
		setLoggerAppend(logger, appenderList);
		return logger;
	}

	// *******************************************************
	// 控制台日志                                             *
	// *******************************************************

	/**
	 * 控制台日志打印，默认debug
	 *
	 * @param clazz
	 * @return
	 */
	public static Logger getLogger(Class clazz) {
		return getLogger(clazz, Level.DEBUG);
	}

	/**
	 * 控制台日志打印
	 *
	 * @param clazz
	 * @return
	 */
	public static Logger getLogger(Class clazz, Level level) {
		ch.qos.logback.classic.Logger logger = getLogbackLogger(clazz);
		LoggerContext loggerContext = logger.getLoggerContext();
		List<OutputStreamAppender> appenderList = chooseAppender(AppenderGroupMode.ONLY_CONSOLE, null, loggerContext, level);
		setLoggerAppend(logger, appenderList);
		return logger;
	}

	/**
	 * 获取logback logger(相同代码合并)
	 *
	 * @param clazz
	 * @return
	 */
	private static ch.qos.logback.classic.Logger getLogbackLogger(Class clazz) {
		return (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(clazz);
	}

	/**
	 * 设置logger的输出器
	 *
	 * @param logger
	 * @param appenderList
	 */
	@SuppressWarnings("unchecked")
	private static void setLoggerAppend(ch.qos.logback.classic.Logger logger, List<OutputStreamAppender> appenderList) {
		logger.setAdditive(false);
		for (OutputStreamAppender appender : appenderList) {
			logger.addAppender(appender);
		}
	}

	/**
	 * 根据模式选择创建appender列表，特定模式不需要的内容参数传入null
	 *
	 * @param mode
	 * @param logFileName
	 * @param loggerContext
	 * @param level
	 * @return
	 */
	private static List<OutputStreamAppender> chooseAppender(AppenderGroupMode mode, LogFileType logFileName,
															 LoggerContext loggerContext, Level level) {

		List<OutputStreamAppender> appenderList = new ArrayList<>();
		switch (mode) {
			case ONLY_FILE:
				RollingFileAppender errorAppenderCommon = createFileAppender(logFileName.getType(), Level.ERROR, loggerContext);
				RollingFileAppender infoAppenderCommon = createFileAppender(logFileName.getType(), Level.INFO, loggerContext);
				appenderList.add(errorAppenderCommon);
				appenderList.add(infoAppenderCommon);
				break;
			case ONLY_CONSOLE:
				Optional<ConsoleAppender> consoleAppenderCommon = Optional.ofNullable(defaultConsoleAppender);
				ConsoleAppender realConsoleAppenderCommon = consoleAppenderCommon.orElse(createConsoleAppender(loggerContext, level));
				appenderList.add(realConsoleAppenderCommon);
				break;
			case CONSOLE_AND_FILE:
				RollingFileAppender errorAppender = createFileAppender(logFileName.getType(), Level.ERROR, loggerContext);
				RollingFileAppender infoAppender = createFileAppender(logFileName.getType(), Level.INFO, loggerContext);
				Optional<ConsoleAppender> consoleAppender = Optional.ofNullable(defaultConsoleAppender);
				ConsoleAppender realConsoleAppender = consoleAppender.orElse(createConsoleAppender(loggerContext, level));
				appenderList.add(errorAppender);
				appenderList.add(infoAppender);
				appenderList.add(realConsoleAppender);
				break;
			default:
				throw new AppenderGroupModeChooseException("Logback logger factory get appender list when choosing appender, parameter mode: ", mode);
		}
		return appenderList;
	}

	/**
	 * 创建日志文件的file appender
	 *
	 * @param name
	 * @param level
	 * @param loggerContext
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private static RollingFileAppender createFileAppender(String name, Level level, LoggerContext loggerContext) {
		RollingFileAppender appender = new RollingFileAppender();
		// 这里设置级别过滤器
//		appender.addFilter(createLevelFilter(level));
		// 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
		// 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
		appender.setContext(loggerContext);
		// appender的name属性
		appender.setName(name.toUpperCase() + "-" + level.levelStr.toUpperCase());
		// 读取logback配置文件中的属性值，设置文件名
		String logPath = logbackLogbackProperties.getLogPath() + File.separatorChar + name + "-" + level.levelStr.toLowerCase() + ".log";
		appender.setFile(logPath);
		appender.setAppend(true);
		appender.setPrudent(false);
		// 加入下面两个节点
		appender.setRollingPolicy(createRollingPolicy(name, level, loggerContext, appender));
		appender.setEncoder(createEncoder(loggerContext, ColorfulLogTheme.resolvePattern(colorfulLoggerProperties.getLogFilePattern())));
		appender.start();
		return appender;
	}

	/**
	 * 创建控制台appender
	 *
	 * @param loggerContext
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private static ConsoleAppender createConsoleAppender(LoggerContext loggerContext, Level level) {
		ConsoleAppender appender = new ConsoleAppender();
		appender.setContext(loggerContext);
		appender.setName(CONSOLE_APPENDER_NAME);
//		appender.addFilter(createLevelFilter(level));
		appender.setEncoder(createEncoder(loggerContext, ColorfulLogTheme.resolvePattern(colorfulLoggerProperties.getConsolePattern())));
		appender.start();
		return appender;
	}


	/**
	 * 设置日志的滚动策略
	 *
	 * @param logName
	 * @param level
	 * @param context
	 * @param appender
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private static TimeBasedRollingPolicy createRollingPolicy(String logName, Level level, LoggerContext context, FileAppender appender) {
		// 设置历史文件名，等同<FileNamePattern>
		String fileNamePattern = logbackLogbackProperties.getLogPath() + File.separatorChar + logbackLogbackProperties.getLogNamePrefix()
				+ "-" + logName + "-" + level.levelStr.toLowerCase() + "_%d{yyyy-MM-dd}_%i.log";

		TimeBasedRollingPolicy rollingPolicyBase = new TimeBasedRollingPolicy<>();
		// 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
		// 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
		rollingPolicyBase.setContext(context);
		// 设置父节点是appender
		rollingPolicyBase.setParent(appender);
		// 设置文件名模式
		rollingPolicyBase.setFileNamePattern(fileNamePattern);
		SizeAndTimeBasedFNATP sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP();
		// 最大日志文件大小
		sizeAndTimeBasedFNATP.setMaxFileSize(FileSize.valueOf(logbackLogbackProperties.getSingleMaxFileSize()));
		rollingPolicyBase.setTimeBasedFileNamingAndTriggeringPolicy(sizeAndTimeBasedFNATP);
		// 设置最大历史记录为x天
		rollingPolicyBase.setMaxHistory(logbackLogbackProperties.getMaxHistory());
		// 总大小限制
		rollingPolicyBase.setTotalSizeCap(FileSize.valueOf(logbackLogbackProperties.getTotalMaxFileSize()));
		rollingPolicyBase.start();

		return rollingPolicyBase;
	}

	/**
	 * 设置日志输出格式
	 *
	 * @param context
	 * @param logPattern
	 * @return
	 */
	private static PatternLayoutEncoder createEncoder(LoggerContext context, String logPattern) {
		PatternLayoutEncoder encoder = new PatternLayoutEncoder();
		// 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
		// 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
		encoder.setContext(context);
		// 设置格式
		encoder.setPattern(logPattern);
		encoder.setCharset(StandardCharsets.UTF_8);
		encoder.start();
		return encoder;
	}

	/**
	 * 设置打印日志的级别
	 *
	 * @param level
	 * @return
	 */
	private static Filter createLevelFilter(Level level) {
		LevelFilter levelFilter = new LevelFilter();
		levelFilter.setLevel(level);
		levelFilter.setOnMatch(FilterReply.ACCEPT);
		levelFilter.setOnMismatch(FilterReply.DENY);
		levelFilter.start();
		return levelFilter;
	}

	/**
	 * 读取logback配置文件中的所有appender
	 *
	 * @return
	 */
	private static Map<String, Appender<ILoggingEvent>> logbackAllConfigurationAppender() {
		Map<String, Appender<ILoggingEvent>> appenderMap = new HashMap<>();
		LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
		for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) {
			for (Iterator<Appender<ILoggingEvent>> index = logger.iteratorForAppenders(); index.hasNext(); ) {
				Appender<ILoggingEvent> appender = index.next();
				appenderMap.put(appender.getName(), appender);
			}
		}
		return appenderMap;
	}
}
